﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.ESB.Adapter;
using Microsoft.Practices.ESB.Exception.Management;
using Microsoft.Practices.ESB.GlobalPropertyContext;
using Microsoft.Practices.ESB.Itinerary;
using Microsoft.Practices.ESB.Itinerary.Services;
using Microsoft.Practices.ESB.Resolver;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Streaming;
using System.IO;
using System.Reflection;
using System.Xml;
using BizTalk.ESB.Extensions.Helpers;

namespace BizTalk.ESB.Extensions.MessagingServices
{
    public class PropertyPromoter : IMessagingService
    {
        IBaseMessage IMessagingService.Execute(IPipelineContext context, IBaseMessage msg, string resolverString, IItineraryStep step)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (msg == null)
                throw new ArgumentNullException("msg");
            if (string.IsNullOrEmpty(resolverString))
                throw new ArgumentException("Resolver must be provided", "resolverString");
            try
            {
                ResolverInfo info = ResolverMgr.GetResolverInfo(ResolutionType.Endpoint, resolverString);
                if (!info.Success)
                    throw new RoutingException("Invalid Resolver", resolverString);

                // Resolve configuration for routing.
                Dictionary<string, string> resolverDictionary = ResolverMgr.Resolve(info, msg, context);

                var contextInfo = resolverDictionary["Resolver.PropertyPromotionConfig"];

                // Get Message as XmlDocument
                XmlDocument xmlMessage = new XmlDocument();
                xmlMessage.LoadXml(this.GetMessageContent(context, msg));

                if (msg.BodyPart != null && (!string.IsNullOrEmpty(contextInfo)))
                {
                    var originalStrm = msg.BodyPart.GetOriginalDataStream();

                    if (originalStrm != null)
                    {
                        // TODO: Promote Properties
                        var propertyConfig = new ItineraryServiceDataMappingConfig();
                        // This creates dictionary items where they key is the proprety to promote (http://mynamespace/properties#propertyname)
                        // and the value to use when promoting
                        var propertyMappings = propertyConfig.BuildDataMappingDictionary(contextInfo, xmlMessage, msg.Context, false);
                        foreach (var p in propertyMappings)
                        {
                            PromoteProperty(msg, p.Key, p.Value);
                        }
                    }
                }

                return msg;
            }
            catch (System.Exception ex)
            {
                EventLogger.Write(MethodInfo.GetCurrentMethod(), ex);
                throw;
            }    
        }

        string IMessagingService.Name
        {
            get { return "BizTalk.Core.Itinerary.MessagingServices.PropertyPromoter"; }
        }

        bool IMessagingService.ShouldAdvanceStep(IItineraryStep step, IBaseMessage msg)
        {
            return true;
        }

        bool IMessagingService.SupportsDisassemble
        {
            get { return false; }
        }

        private String GetMessageContent(IPipelineContext pipeLineContext, IBaseMessage baseMessage)
        {
            //extract message
            var message = new StringBuilder(); //Save message in this StringBuilder
            var copyStream = new VirtualStream(); //Copy the original stream to the a new stream 
            var copyStreamWriter = new StreamWriter(copyStream); //Use a stream writer to copy original stream to copyStream
            ReadOnlySeekableStream seekableStream = new ReadOnlySeekableStream(baseMessage.BodyPart.GetOriginalDataStream());

            //Read OriginalStream
            using (StreamReader streamReader = new StreamReader(seekableStream))
            {

                var line = string.Empty; //Read Stream Line by Line
                while ((line = streamReader.ReadLine()) != null)
                {
                    message.AppendLine(line); //Save Message to StringBuilder
                    copyStreamWriter.WriteLine(line); //Copy to new stream
                }

                copyStreamWriter.Flush(); //Ensure everything has been written to the new stream
                copyStream.Seek(0, SeekOrigin.Begin); //Set the new stream to the beginning
                baseMessage.BodyPart.Data = copyStream; //Set the Stream of the message leaving it's integrity intact

                pipeLineContext.ResourceTracker.AddResource(copyStream); //Ensure these resources are tracked and ultimately disposed
                pipeLineContext.ResourceTracker.AddResource(copyStreamWriter); //Ensure these resources are tracked and ultimately disposed

            }
            return message.ToString();
        }

        private void PromoteProperty(IBaseMessage msg, string destinationPropertyKey, string propertyValue)
        {
            if (string.IsNullOrEmpty(destinationPropertyKey))
                throw new ArgumentNullException("Promoted Property", "Undefined Promoted Property");

            if (!string.IsNullOrEmpty(propertyValue))
            {
                var destinationPropertyParts = destinationPropertyKey.Split("#".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                if (destinationPropertyParts.Length != 2)
                    throw new ArgumentException("Promoted Property", "Invalid Promoted Property Key.  Should be [namespace]#[name]");
                var propertyNamespace = destinationPropertyParts[0];
                var propertyName = destinationPropertyParts[1];
                msg.Context.Promote(propertyName, propertyNamespace, propertyValue);
            }
        }
    }
}
